package sindconsole.auth.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import common.mybatis.Queries;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import sindconsole.auth.bean.Account;
import sindconsole.auth.bean.Permission;
import sindconsole.auth.bean.Role;
import sindconsole.auth.bean.User;
import sindconsole.auth.dao.AccountMapper;
import sindconsole.auth.dao.RoleMapper;
import sindconsole.auth.dao.UserMapper;

import javax.annotation.Nullable;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    AccountMapper accountMapper;

    @Autowired
    RoleMapper roleMapper;

    @Autowired
    UserMapper userMapper;

    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

    AuthPredefinitions authPredefinitions = AuthPredefinitions.load();

    @Override
    public User findUser(long id) {
        return userMapper.selectById(id);
    }

    @Override
    public Page<User> findUsers(@Nullable String keyword, @Nullable Date createdTime0, @Nullable Date createdTime1, int... pagination) {
        LambdaQueryWrapper<User> criteria = new QueryWrapper<User>().lambda();
        if (keyword != null)
            criteria.eq(User::getId, keyword).or().like(User::getNickname, keyword).or().like(User::getPhone, keyword).or().like(User::getMail, keyword);
        if (createdTime0 != null) criteria.ge(User::getCreatedTime, createdTime0);
        if (createdTime1 != null) criteria.lt(User::getCreatedTime, createdTime1);
        return Queries.paginate(() -> userMapper.selectList(criteria), pagination);
    }

    @Override
    public int createUser(User user) {
        if (user.getStatus() == null) user.setStatus((short) 0);
        user.setCreatedTime(new Date());
        return userMapper.insert(user);
    }

    @Override
    public int updateUser(User user) {
        User oldUser = findUser(user.getId());
        if (StringUtils.hasText(user.getPhone()) && !user.getPhone().equals(oldUser.getPhone()))
            user.setPhoneStatus((short) 0);
        if (StringUtils.hasText(user.getMail()) && !user.getMail().equals(oldUser.getMail()))
            user.setMailStatus((short) 0);
        user.setUpdatedTime(new Date());
        return userMapper.updateById(user);
    }

    @Override
    public Permission findPermission(String key) {
        return authPredefinitions.findPermission(key);
    }

    @Override
    public List<Permission> findPermissions() {
        return authPredefinitions.findPermissions();
    }

    @Override
    public Role findRole(String key) {
        Role role = authPredefinitions.findRole(key);
        if (role == null) role = roleMapper.selectById(key);
        return role;
    }

    @Override
    public List<Role> findRoles() {
        List<Role> roles = authPredefinitions.findRoles();
        roles.addAll(roleMapper.selectAll());
        return roles;
    }

    @Override
    public int createRole(Role role) {
        return roleMapper.insert(role);
    }

    @Override
    @Transactional
    public int updateRole(Role role, String oldKey) {
        int r = roleMapper.update(role, oldKey);
        if (!role.getKey().equals(oldKey)) {
            r += roleMapper.updateAllUserRole(role.getKey(), oldKey);
            r += roleMapper.updateAllRolePermission(role.getKey(), oldKey);
        }
        return r;
    }

    @Override
    @Transactional
    public int deleteRole(String key) {
        int r = roleMapper.delete(key);
        r += roleMapper.deleteAllUserRole(key);
        r += roleMapper.deleteAllRolePermission(key);
        return r;
    }

    @Override
    public Account findAccount(String username) {
        return accountMapper.selectById(username);
    }

    @Override
    public List<Account> findUserAccounts(long id) {
        return accountMapper.selectByUserId(id);
    }

    @Override
    public User findUserByUsername(String username) {
        return accountMapper.selectUserByUsername(username);
    }

    @Override
    public User authc(String username, String password) {
        Account account = accountMapper.selectById(username);
        if (account == null) return null;
        return authc(account, password);
    }

    @Override
    public User authc(Account account, String password) {
        if (!passwordEncoder.matches(password, account.getPassword()))
            throw new BadCredentialsException(password);
        return userMapper.selectById(account.getUserId());
    }

    @Override
    public int createAccount(Account account) {
        if (StringUtils.hasText(account.getPlainPassword()))
            account.setPassword(passwordEncoder.encode(account.getPlainPassword()));
        account.setCreatedTime(new Date());
        return accountMapper.insert(account);
    }

    @Override
    @Transactional
    public int createUserAndAccount(User user, String username, String password) {
        int r = createUser(user);
        if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
            Account account = new Account();
            account.setUsername(username);
            account.setPlainPassword(password);
            account.setUserId(user.getId());
            r += createAccount(account);
        }
        return r;
    }

    @Override
    public int updateAccount(Account account) {
        if (StringUtils.hasText(account.getPlainPassword()))
            account.setPassword(passwordEncoder.encode(account.getPlainPassword()));
        return accountMapper.updateById(account);
    }

    @Override
    @Transactional
    public int updateUserAndPassword(User user, String username, String password) {
        int r = updateUser(user);
        Account account = accountMapper.selectById(username);
        if (account == null && StringUtils.hasText(username) && StringUtils.hasText(password)) {
            account = new Account();
            account.setUsername(username);
            account.setPlainPassword(password);
            account.setUserId(user.getId());
            r += createAccount(account);
        } else if (account != null && StringUtils.hasText(password)) {
            r += updateUserPassword(user.getId(), password);
        }
        return r;
    }

    @Override
    public int updateUserPassword(long userId, String password) {
        String encodedPassword = passwordEncoder.encode(password);
        return accountMapper.updatePasswordByUserId(userId, encodedPassword);
    }

    @Override
    public List<Permission> findUserPermissions(long id) {
        return userMapper.selectUserPermissionKeys(id).stream().map(key -> authPredefinitions.findPermission(key)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findUserPermissionKeys(long id) {
        return userMapper.selectUserPermissionKeys(id).stream().collect(Collectors.toSet());
    }

    @Override
    public Set<String> findAllUserPermissionKeys(long id) {
        Set<String> result = findUserPermissionKeys(id);
        Set<String> userRoleKeys = findUserRoleKeys(id);
        for (String userRoleKey : userRoleKeys) {
            result.addAll(findRolePermissionKeys(userRoleKey));
        }
        return result;
    }

    @Override
    @Transactional
    public int giveUserPermissions(long id, String... permissionKeys) {
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += userMapper.insertUserPermission(id, permissionKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveUserPermissions(long id, String... permissionKeys) {
        if (permissionKeys.length == 0) return userMapper.deleteAllUserPermission(id);
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += userMapper.deleteUserPermission(id, permissionKey);
        }
        return result;
    }

    @Override
    public List<Role> findUserRoles(long id) {
        return userMapper.selectUserRoleKeys(id).stream().map(key -> findRole(key)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findUserRoleKeys(long id) {
        return userMapper.selectUserRoleKeys(id).stream().collect(Collectors.toSet());
    }

    @Override
    @Transactional
    public int giveUserRoles(long id, String... roleKeys) {
        int result = 0;
        for (String roleKey : roleKeys) {
            result += userMapper.insertUserRole(id, roleKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveUserRoles(long id, String... roleKeys) {
        if (roleKeys.length == 0) return userMapper.deleteAllUserPermission(id);
        int result = 0;
        for (String roleKey : roleKeys) {
            result += userMapper.deleteUserRole(id, roleKey);
        }
        return result;
    }

    @Override
    public List<Permission> findRolePermission(String key) {
        Role role = findRole(key);
        if (role.isPredefined())
            return ((AuthPredefinitions.RoleInfo) role).getPermissions().stream().map(s -> findPermission(s)).collect(Collectors.toList());
        else
            return roleMapper.selectRolePermissionKeys(key).stream().map(s -> findPermission(s)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findRolePermissionKeys(String key) {
        Role role = findRole(key);
        if (role.isPredefined())
            return ((AuthPredefinitions.RoleInfo) role).getPermissions().stream().collect(Collectors.toSet());
        else
            return roleMapper.selectRolePermissionKeys(key).stream().collect(Collectors.toSet());
    }

    @Override
    @Transactional
    public int giveRolePermissions(String key, String... permissionKeys) {
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += roleMapper.insertRolePermission(key, permissionKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveRolePermissions(String key, String... permissionKeys) {
        if (permissionKeys.length == 0) return roleMapper.deleteAllRolePermission(key);
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += roleMapper.deleteRolePermission(key, permissionKey);
        }
        return result;
    }

    @Override
    public Page<Account> findUserAccount(String keyword, int[] pagination) {
        LambdaQueryWrapper<Account> criteria = new QueryWrapper<Account>().lambda();
        if (keyword != null) criteria.like(Account::getUsername, keyword);
        Page<Account> paginate = Queries.paginate(() -> accountMapper.selectList(criteria), pagination);
        List<Account> accountList = paginate.getContent();
        if (accountList.size() == 0) return paginate;
        Set<Long> userIds = accountList.stream().map(Account::getUserId).collect(Collectors.toSet());
        List<User> users = userMapper.selectBatchIds(userIds);
        for (Account account : accountList) {
            List<User> userList =
                    users.stream().filter(u -> Objects.equals(u.getId(), account.getUserId())).collect(Collectors.toList());
            if (userList.size() == 1) {
                account.setUser(userList.get(0));
            }
        }
        return paginate;
    }
}
